#include "common.hpp"
#include "finaly.hpp"
#include "fs.hpp"
#include <cstring>
#include <dirent.h>
#include <limits.h>
#include <math.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#ifndef S_BLKSIZE
// https://github.com/freebsd/freebsd/commit/c6b6ed2e549b753df7d2ecf2f0103b2223698dac
#define S_BLKSIZE 512
#endif

namespace gitee {

using stat_t = struct stat;
using dirent_t = struct dirent;

class DirAnalyzer {
public:
  DirAnalyzer() = default;
  ~DirAnalyzer() = default;
  DirAnalyzer(const DirAnalyzer &) = delete;
  DirAnalyzer &operator=(const DirAnalyzer &) = delete;
  int64_t size() const { return size_; }
  bool Du(gitee::string_view dir, int md = 8) {
    maxDepth = md;
    stat_t st;
    if (stat(dir.data(), &st) != 0) {
      return false;
    }
    dirsize = st.st_blocks * S_BLKSIZE;
    return DuInternal(dir);
  }

private:
  bool DuInternal(gitee::string_view dir);
  int64_t size_{0};
  int64_t dirsize{8 * S_BLKSIZE};
  int maxDepth{8};
};

bool DirAnalyzer::DuInternal(gitee::string_view dir) {
  DIR *d = opendir(dir.data());
  if (d == nullptr) {
    return false;
  }
  maxDepth--;
  auto deleter = bela::finally([&] {
    maxDepth++;
    closedir(d);
  });
  size_ += dirsize;
  // skip depth
  if (maxDepth < 0) {
    return true;
  }
  DirBuffer db(dir);
  dirent *dt = nullptr;
  while ((dt = readdir(d)) != nullptr) {
    if (DirSkipFaster(dt->d_name)) {
      continue;
    }
    if (dt->d_type == DT_DIR) {
      db.Append(dt->d_name);
      DuInternal(db.sv());
      continue;
    }
    stat_t st;
    db.Append(dt->d_name);
    if (stat(db.data(), &st) == 0) {
      size_ += st.st_blocks * S_BLKSIZE;
    }
  }
  return true;
}

inline gitee::string_view PathRemoveSpecSuffix(gitee::string_view sv) {
  auto pos = sv.find_last_not_of('/');
  if (pos == gitee::string_view::npos) {
    return "/";
  }
  auto n = sv.size() - pos - 1;
  if (n > 0) {
    sv.remove_suffix(n);
  }
  return sv;
}

bool CalculateRepoSize(gitee::string_view dir, int64_t &size) {
  dir = PathRemoveSpecSuffix(dir); /// cleanup dir;
  if (!gitee::ConsumeSuffix(&dir, ".wiki.git")) {
    dir = gitee::StripSuffix(dir, ".git");
  }
  auto gitdir = gitee::StrCat(dir, ".git/objects");
  DirAnalyzer da;
  if (!da.Du(gitdir)) {
    return false;
  }
  size = da.size();
  auto wikidir = gitee::StrCat(dir, ".wiki.git/objects");
  DirAnalyzer wda;
  if (wda.Du(wikidir)) {
    size += wda.size();
  }
  return true;
}

} // namespace gitee
